package main

import (
	"bytes"
	"encoding/xml"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"text/template"
	"zstackgo/spring"

	// "github.com/davecgh/go-spew/spew"
	"github.com/meilihao/golib/v2/log"
	"github.com/spf13/cobra"
	"go.uber.org/zap"
)

var (
	componentName = "parseSpringConfigXml"

	rootCmd = &cobra.Command{
		Use:   componentName,
		Short: "",
		Run:   rootRun,
	}

	opt = struct {
		SpringConfigXml string
		TargetPath      string
		Debug           bool
	}{}
)

func init() {
	rootCmd.PersistentFlags().StringVar(&opt.SpringConfigXml, "sp", "/opt/mark/test/zstack-repos/zstack/conf/springConfigXml", "SpringConfigXml's path")
	rootCmd.PersistentFlags().StringVar(&opt.TargetPath, "tp", "/home/chen/git/zstackgo/pkg", "save generate to")
	rootCmd.PersistentFlags().BoolVar(&opt.Debug, "d", false, "for debug")
}

func main() {
	rootCmd.Execute()
}

func rootRun(cmd *cobra.Command, args []string) {
	files, err := filepath.Glob(opt.SpringConfigXml + "/*.xml")
	if err != nil {
		log.Glog.Error("glob SpringConfigXml", zap.Error(err))
	}
	log.Glog.Info("found xml", zap.Int("num", len(files)))

	for _, fp := range files {
		log.Glog.Info("found xml", zap.String("name", filepath.Base(fp)))
		// fp = "/opt/mark/test/zstack-repos/zstack/conf/springConfigXml/ansibleFacade.xml" // for debug
		ParseSpringConfigXml(fp)

		if !opt.Debug && fp == "/opt/mark/test/zstack-repos/zstack/conf/springConfigXml/ansibleFacade.xml" {
			break
		}
	}
}

func ParseSpringConfigXml(fp string) (err error) {
	log.Glog.Info("start parse", zap.String("name", filepath.Base(fp)))

	data, err := os.ReadFile(fp)
	if err != nil {
		log.Glog.Fatal("load xml data", zap.Error(err))
	}

	conf := new(BeanConf)
	if err = xml.Unmarshal(data, conf); err != nil {
		log.Glog.Fatal("unmarshal xml", zap.Error(err))
	}

	// spew.Dump(conf)

	for i := range conf.Beans {
		HandleBean(filepath.Base(fp), &conf.Beans[i])
	}

	log.Glog.Info("end parse", zap.String("name", filepath.Base(fp)))

	return
}

const (
	ZstackPkgPrefix = "org.zstack."
)

func HandleBean(fn string, b *Bean) {
	log.Glog.Info("start bean", zap.String("name", fn), zap.String("bean", b.Class))

	if strings.HasPrefix(b.Class, ZstackPkgPrefix) {
		b.Class = strings.TrimPrefix(b.Class, ZstackPkgPrefix)
	} else {
		log.Glog.Info("is not zstack bean")

		// for DatabaseFacade.xml RESTFacade.xml
		tmp := strings.Split(b.Class, ".")
		tmp = append([]string{"todo"}, tmp[len(tmp)-2:]...)
		b.Class = strings.Join(tmp, ".")
	}

	if len(b.Plugins) > 1 {
		log.Glog.Fatal("too many plugin tag")
	}

	for i, p := range b.Plugins {
		for j, _ := range p.Exts {
			if !strings.HasPrefix(b.Plugins[i].Exts[j].Interface, ZstackPkgPrefix) {
				log.Glog.Fatal("no ZstackPkgPrefix for extension enterface")
			}
			b.Plugins[i].Exts[j].Interface = strings.TrimPrefix(b.Plugins[i].Exts[j].Interface, ZstackPkgPrefix)
		}
	}

	ps := strings.Split(b.Class, ".")

	// if b.Id != ps[len(ps)-1] { // for test b.Id == beanName
	// 	panic(fmt.Sprintf("bean id(%s) not match name(%s)", b.Id, ps[len(ps)-1]))
	// }
	// return

	sfp := filepath.Join(opt.TargetPath, strings.Join(ps[:len(ps)-1], "/"), ps[len(ps)-1]+".go")
	sfi, _ := os.Stat(sfp)
	if sfi == nil {
		log.Glog.Info("no gofile", zap.String("file", sfp))

		GenerateBean(b, sfp)
	} else {
		log.Glog.Info("--- found gofile", zap.String("file", sfp))

		data, _ := os.ReadFile(sfp)
		if bytes.Contains(data, []byte("ImplPlugins() []string")) {
			os.Remove(sfp)
			GenerateBean(b, sfp)
		}
	}

	// handle interace
	for _, p := range b.Plugins {
		for _, e := range p.Exts {
			ips := strings.Split(e.Interface, ".")
			isfp := filepath.Join(opt.TargetPath, strings.Join(ips[:len(ips)-1], "/"), ips[len(ips)-1]+".go")

			isfi, _ := os.Stat(isfp)
			if isfi == nil {
				log.Glog.Info("no igofile", zap.String("file", isfp))

				GenerateInterface(e, isfp)
			} else {
				log.Glog.Info("### found igofile", zap.String("file", isfp))

				data, _ := os.ReadFile(isfp)
				if bytes.Contains(data, []byte("spring.RegisterInterface(")) {
					os.Remove(isfp)
					GenerateInterface(e, isfp)
				}
			}
		}
	}

	log.Glog.Info("end bean", zap.String("name", fn), zap.String("bean", b.Class))

}

func GenerateBean(b *Bean, fp string) {
	ps := strings.Split(b.Class, ".")

	t := GoSource{
		PkgName:    ps[len(ps)-2],
		StructName: ps[len(ps)-1],
		StructId:   b.Id,
	}
	if len(b.Plugins) != 0 {
		for _, v := range b.Plugins[0].Exts {
			var order int64 = 0
			if v.Order != "" {
				order, _ = strconv.ParseInt(v.Order, 10, 64)
			}

			t.Exts = append(t.Exts, spring.PluginExtension{
				Interface:   v.Interface,
				InstanceRef: v.InstanceRef,
				Order:       int(order),
			})
		}
	}

	tmpl, err := template.New("").Parse(SourceTmp)
	if err != nil {
		log.Glog.Fatal("load source tmpl", zap.Error(err))
	}

	buf := bytes.NewBuffer(nil)
	if err = tmpl.Execute(buf, t); err != nil {
		log.Glog.Fatal("render source tmpl", zap.Error(err))
	}

	sfd := filepath.Dir(fp)
	if err = os.MkdirAll(sfd, 0775); err != nil {
		log.Glog.Fatal("make source dir", zap.Error(err))
	}
	if err = os.WriteFile(fp, buf.Bytes(), 0660); err != nil {
		log.Glog.Fatal("wirte source", zap.Error(err))
	}
}

func GenerateInterface(e Extension, fp string) {
	ps := strings.Split(e.Interface, ".")

	t := GoSourceInterface{
		PkgName:       ps[len(ps)-2],
		InterfaceName: ps[len(ps)-1],
		InterfaceId:   e.Interface,
	}

	tmpl, err := template.New("").Parse(InterfaceTmp)
	if err != nil {
		log.Glog.Fatal("load interface tmpl", zap.Error(err))
	}

	buf := bytes.NewBuffer(nil)
	if err = tmpl.Execute(buf, t); err != nil {
		log.Glog.Fatal("render interface tmpl", zap.Error(err))
	}

	sfd := filepath.Dir(fp)
	if err = os.MkdirAll(sfd, 0775); err != nil {
		log.Glog.Fatal("make interface dir", zap.Error(err))
	}
	if err = os.WriteFile(fp, buf.Bytes(), 0660); err != nil {
		log.Glog.Fatal("wirte interface", zap.Error(err))
	}
}

// to use ast
// func WriteToGoFile(wr io.Writer, funcs []ast.Decl) error {
// 	// 输出Go代码
// 	header := `// Code generated by parseSpringConfigXml.
// `
// 	buffer := bytes.NewBufferString(header)

// 	for _, fn := range funcs {
// 		err := astToGo(buffer, fn)
// 		if err != nil {
// 			return errx.WithStackOnce(err)
// 		}
// 	}

// 	_, err := wr.Write(buffer.Bytes())
// 	return err
// }
